Skip to content

Conversation

@hannesrudolph
Copy link
Collaborator

@hannesrudolph hannesrudolph commented Nov 14, 2025

Summary

Adds automatic task history cleanup to help manage disk space. Old tasks and their associated checkpoints are automatically removed when VS Code restarts.

User-Facing Changes

New Setting: Auto-Delete Task History

Access via the gear icon in the History View header. Choose how long to keep your task history:

  • Never (default) – Keep all tasks forever
  • 90, 60, 30, 7, or 3 days

Safety Features

  • Confirmation dialog – Changing the retention period requires explicit confirmation to prevent accidental data loss
  • Clear warning message – "When enabled, tasks older than the selected period are permanently deleted when VS Code restarts"
  • Task count display – See how many tasks you have in history (e.g., "42 tasks in history")

What Gets Cleaned Up

When VS Code restarts (if a retention period is set):

  • Tasks older than your chosen period are deleted
  • Associated checkpoints and shadow repositories are removed
  • Orphan checkpoint-only folders are detected and cleaned up

Full Localization

All new UI text is translated into 18 languages.

Screenshots

History View with gear icon settings popover

Closes #10850

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. Enhancement New feature or request labels Nov 14, 2025
@roomote
Copy link
Contributor

roomote bot commented Nov 14, 2025

Oroocle Clock   See task on Roo Cloud

Re-review complete for 9c92200c. No new issues spotted in the latest change set.

TODO

  • Fix non-English settings:taskHistoryStorage.* keys to match the new task-count UI (clickToCount/count/countSingular) so locales don't render missing-key fallbacks.
  • Make task-storage-size.spec.ts deterministic (avoid <100ms timing assertion).
  • Improve calculateTaskStorageSize() logging so readdir permission/IO errors aren't reported as "directory not found".
  • Ensure retention purge does not delete tasks in parallel when using provider-backed deletion (avoid taskHistory state races).
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

@hannesrudolph hannesrudolph added the Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. label Nov 14, 2025
@hannesrudolph hannesrudolph force-pushed the chore/retention-purge-checkpoint-cleanup branch from 784a70d to 67c7629 Compare November 19, 2025 08:57
@hannesrudolph hannesrudolph force-pushed the chore/retention-purge-checkpoint-cleanup branch from d9b68d5 to 42d73fe Compare November 26, 2025 19:47
@hannesrudolph hannesrudolph moved this from Triage to PR [Needs Prelim Review] in Roo Code Roadmap Nov 26, 2025
@hannesrudolph hannesrudolph added PR - Needs Preliminary Review and removed Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. labels Nov 26, 2025
@hannesrudolph hannesrudolph force-pushed the chore/retention-purge-checkpoint-cleanup branch from 84853c9 to cc67f71 Compare January 7, 2026 16:38
Copy link
Contributor

@heyseth heyseth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executive Summary

This PR introduces an auto-delete task history feature that purges old tasks on extension reload. The implementation is well-architected, type-safe, and thoroughly tested. The code demonstrates solid engineering practices with proper error handling, i18n support, and integration with existing deletion logic.

Strengths

  • Consistency: Excellent reuse of existing deleteTaskWithId() logic ensures checkpoints, shadow repos, and state are cleaned up alongside files.
  • Reliability: Sequential deletion prevents race conditions, and robust retry logic handles stubborn filesystem operations.
  • Quality: Full i18n support across 17 languages and type-safe schema validation with Zod.

Suggested Enhancements (Code Included)

I have included specific code snippets in the comments to address performance and user trust:

1. Performance: Non-blocking Activation (Critical)
The current implementation awaits the purge process during the activate phase. On machines with large histories or slow disks, this will block the extension startup.

  • Fix: I provided a snippet to wrap the logic in a background "fire-and-forget" task (void (async ...)), ensuring the extension activates immediately.
  • Optimization: Added an early return check for retention === "never" to skip the logic entirely for the majority of users.

2. UX: Visibility & Trust
Silent deletion of user data can be alarming if a user expects to find a task.

  • Fix: I added a Toast Notification logic to the background task. It only triggers if files were actually deleted and provides a direct link to Settings.

3. Testing: Edge Case Coverage
Since we are permanently deleting files, I identified a gap in testing regarding "Orphan Checkpoints" and "Legacy Mtime Fallback."

  • Fix: I drafted 4 specific test cases to verify we strictly target garbage data and do not accidentally delete recent, valid tasks that lack metadata.

Conclusion

This is a high-value maintenance feature. Once the activation blocking issue is resolved (using the background task pattern I suggested), this logic is solid and ready for production!

@hannesrudolph hannesrudolph force-pushed the chore/retention-purge-checkpoint-cleanup branch from cc67f71 to ab6d3ab Compare January 20, 2026 01:43
@hannesrudolph hannesrudolph force-pushed the chore/retention-purge-checkpoint-cleanup branch from 098aa38 to 18894da Compare January 26, 2026 19:53
Copy link
Contributor

@roomote roomote bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All previously flagged issues have been addressed in the latest commits. The implementation is clean and well-structured:

  • Sequential task deletion prevents race conditions
  • i18n translations added to multiple locales
  • Tests properly mock storage module
  • UI copy accurately describes activation behavior
  • Validation occurs appropriately at use time

No net new issues identified. Approving this PR.

- Remove expensive recursive getDirectorySize() that caused UI freeze with ~9000 tasks
- Now only counts top-level task directories via single fs.readdir() call
- Make task count on-demand (user must click refresh) instead of auto-trigger on settings load
- Update types, i18n strings, and tests accordingly
This PR was never merged, so no need for backwards compatibility.
Import formatBytes directly from its source file.
"format": "{{size}} ({{count}} Aufgaben)",
"formatSingular": "{{size}} (1 Aufgabe)",
"empty": "Keine Aufgaben gespeichert",
"refresh": "Aktualisieren",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-English locales still define settings:taskHistoryStorage.* as storage-size strings (e.g. format, calculating, error), but About.tsx now reads clickToCount, count, and countSingular. This will likely show missing-key fallbacks in most languages unless the locale JSONs are updated to match the new contract.

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines +146 to +153
const startTime = Date.now()
const result = await calculateTaskStorageSize("/global/storage")
const elapsed = Date.now() - startTime

expect(result.taskCount).toBe(9000)
// Should complete quickly since we're not recursing into directories
expect(elapsed).toBeLessThan(100) // Should be nearly instant
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is timing-based ("< 100ms") and can be flaky on slower CI or under load even if the implementation is correct. A more deterministic check is to assert we only do a single top-level readdir call (no recursion).

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines +51 to +58
try {
const entries = await fs.readdir(tasksDir, { withFileTypes: true })
taskCount = entries.filter((d) => d.isDirectory()).length
} catch {
// Tasks directory doesn't exist yet
log?.(`[TaskStorageSize] Tasks directory not found at ${tasksDir}`)
return defaultResult
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch here treats all readdir failures as "tasks directory not found", which is misleading for permission/IO errors and makes debugging harder. Logging the error message preserves the current behavior while keeping diagnostics accurate.

Suggested change
try {
const entries = await fs.readdir(tasksDir, { withFileTypes: true })
taskCount = entries.filter((d) => d.isDirectory()).length
} catch {
// Tasks directory doesn't exist yet
log?.(`[TaskStorageSize] Tasks directory not found at ${tasksDir}`)
return defaultResult
}
} catch (e) {
// Tasks directory doesn't exist yet (or is unreadable)
log?.(
`[TaskStorageSize] Failed to read tasks directory at ${tasksDir}: ${e instanceof Error ? e.message : String(e)}`,
)
return defaultResult
}

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines +146 to +153
const startTime = Date.now()
const result = await calculateTaskStorageSize("/global/storage")
const elapsed = Date.now() - startTime

expect(result.taskCount).toBe(9000)
// Should complete quickly since we're not recursing into directories
expect(elapsed).toBeLessThan(100) // Should be nearly instant
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is timing-based ("< 100ms") and can be flaky on slower CI or under load even if the implementation is correct. A more deterministic check is to assert we only do a single top-level readdir call (no recursion).

Suggested change
const startTime = Date.now()
const result = await calculateTaskStorageSize("/global/storage")
const elapsed = Date.now() - startTime
expect(result.taskCount).toBe(9000)
// Should complete quickly since we're not recursing into directories
expect(elapsed).toBeLessThan(100) // Should be nearly instant
})
const result = await calculateTaskStorageSize("/global/storage")
expect(result.taskCount).toBe(9000)
// Deterministic: only one top-level directory listing (no recursion)
expect(mockReaddir).toHaveBeenCalledTimes(1)

Fix it with Roo Code or mention @roomote and request a fix.

The formatBytes tests are not relevant since we no longer calculate size.
- Add parallel metadata reads with 50 concurrent operations
- Add parallel deletions with 10 concurrent operations
- Batch read all metadata first, then filter, then delete
- Reduce retry attempts from 3 to 1 (remove aggressive retries with sleeps)
- Extract helper functions for cleaner code (readTaskMetadata, pathExists, removeDir)

This significantly improves purge performance for users with many tasks (~9000+).
- Add purgeOldCheckpoints() function with hardcoded 30-day threshold
- Add startBackgroundCheckpointPurge() for fire-and-forget activation
- Culls only checkpoints/ subdirectory, preserves task history
- Runs silently on extension activation (no user notification)
- Coexists with existing taskHistoryRetention setting
- Add 6 new tests for checkpoint culling functionality
logv(`[Retention] Phase 3: Deleting ${tasksToDelete.length} tasks (concurrency: ${DELETION_CONCURRENCY})`)
const deleteLimit = pLimit(DELETION_CONCURRENCY)

const deleteResults = await Promise.all(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This purge still deletes tasks in parallel (Promise.all + p-limit), which can reintroduce the earlier race when the provider updates taskHistory state per deletion. If the purge uses provider-backed deletion (deleteTaskById), consider serializing deletions (for..of) or batching state updates after filesystem cleanup to avoid lost updates.

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines +107 to +113
const startTime = Date.now()
const result = await calculateTaskStorageSize("/global/storage")
const elapsed = Date.now() - startTime

expect(result.taskCount).toBe(9000)
// Should complete quickly since we're not recursing into directories
expect(elapsed).toBeLessThan(100) // Should be nearly instant
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This timing-based assertion ("< 100ms") is likely to be flaky under CI load or slower machines even when behavior is correct; a deterministic alternative is asserting the mocked readdir was called exactly once (no recursion).

Suggested change
const startTime = Date.now()
const result = await calculateTaskStorageSize("/global/storage")
const elapsed = Date.now() - startTime
expect(result.taskCount).toBe(9000)
// Should complete quickly since we're not recursing into directories
expect(elapsed).toBeLessThan(100) // Should be nearly instant
const result = await calculateTaskStorageSize("/global/storage")
expect(result.taskCount).toBe(9000)
// Deterministic: only one top-level directory listing (no recursion)
expect(mockReaddir).toHaveBeenCalledTimes(1)

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines +54 to +57
} catch {
// Tasks directory doesn't exist yet
log?.(`[TaskStorageSize] Tasks directory not found at ${tasksDir}`)
return defaultResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch here logs "Tasks directory not found" for all readdir failures, which is misleading for permission/IO errors. Logging the actual error message keeps behavior the same but makes diagnostics accurate.

Suggested change
} catch {
// Tasks directory doesn't exist yet
log?.(`[TaskStorageSize] Tasks directory not found at ${tasksDir}`)
return defaultResult
} catch (e) {
// Tasks directory doesn't exist yet (or is unreadable)
log?.(
`[TaskStorageSize] Failed to read tasks directory at ${tasksDir}: ${e instanceof Error ? e.message : String(e)}`,
)
return defaultResult
}

Fix it with Roo Code or mention @roomote and request a fix.

- Log number of tasks being scanned
- Log how many tasks have checkpoints
- Log how many need culling (older than 30 days)
- Log progress every 100 deletions during large culls
… popover

- Add Settings gear icon in HistoryView header with popover for retention settings
- Retention dropdown allows configuring auto-delete task history (never, 3-90 days)
- Settings are saved immediately via vscode.postMessage updateSettings
- Remove retention settings from About.tsx (Settings View)
- Update About.tsx props and tests to remove taskHistoryRetention
…aging

- Add confirmation dialog before changing retention settings to prevent accidental changes
- Move task count display from About tab to History View settings popover
- Combine redundant description/warning into single, clear warning message
- Update task count display to be more human-friendly ('X tasks in history')
- Update all 18 locale files with consistent messaging
- Revert About.tsx to original state (no retention UI there)
@hannesrudolph hannesrudolph changed the title Auto-delete task history on plugin load (retention + checkpoint cleanup) feat: auto-delete task history on extension load Jan 27, 2026
The file was left over after size calculation was removed from
task-storage-size.ts for performance reasons. Knip correctly detected
it as unused.
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request lgtm This PR has been approved by a maintainer PR - Needs Preliminary Review size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

Status: PR [Needs Prelim Review]

Development

Successfully merging this pull request may close these issues.

Auto-delete task history with configurable retention

5 participants